{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ee19d298-7a02-4315-add8-45c604c5d9cf",
   "metadata": {},
   "source": [
    "基于此，我们能够实现：\n",
    "- LangChain 的多模块能力（向量搜索 + Agent工具）\n",
    "- Streamlit 前端交互\n",
    "- FAISS 向量数据库\n",
    "- DashScope Embedding + DeepSeek 模型接入\n",
    "- 并完成了完整的 RAG（检索增强生成）流程"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf579a58-5c71-432d-9fc0-705d6518c682",
   "metadata": {},
   "source": [
    "! uv add streamlit PyPDF2 dashscope faiss-cpu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e1ea828-e752-4825-b0e1-3deacf55fe7d",
   "metadata": {},
   "outputs": [],
   "source": [
    "    import streamlit as st\n",
    "    from PyPDF2 import PdfReader\n",
    "    from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
    "    from langchain_core.prompts import ChatPromptTemplate\n",
    "    from langchain_community.vectorstores import FAISS\n",
    "    from langchain.tools.retriever import create_retriever_tool\n",
    "    from langchain.agents import AgentExecutor, create_tool_calling_agent\n",
    "    from langchain_community.embeddings import DashScopeEmbeddings\n",
    "    from langchain.chat_models import init_chat_model\n",
    "    import os\n",
    "    from dotenv import load_dotenv \n",
    "    load_dotenv(override=True)\n",
    "\n",
    "\n",
    "    DeepSeek_API_KEY = os.getenv(\"DEEPSEEK_API_KEY\")\n",
    "    dashscope_api_key = os.getenv(\"dashscope_api_key\")\n",
    "\n",
    "    os.environ[\"KMP_DUPLICATE_LIB_OK\"]=\"TRUE\"\n",
    "\n",
    "# 初始化向量 Embedding 模型\n",
    "    embeddings = DashScopeEmbeddings(\n",
    "        model=\"text-embedding-v1\", dashscope_api_key=dashscope_api_key\n",
    "    )\n",
    "#用阿里云 DashScope 提供的 text-embedding-v1 将文本转为向量表示，用于相似度搜索\n",
    "    def pdf_read(pdf_doc):\n",
    "        text = \"\"\n",
    "        for pdf in pdf_doc:\n",
    "            pdf_reader = PdfReader(pdf)\n",
    "            for page in pdf_reader.pages:\n",
    "                text += page.extract_text()\n",
    "        return text\n",
    "\n",
    "\n",
    "    def get_chunks(text):\n",
    "        text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n",
    "        chunks = text_splitter.split_text(text)\n",
    "        return chunks\n",
    "\n",
    "    def vector_store(text_chunks):\n",
    "        vector_store = FAISS.from_texts(text_chunks, embedding=embeddings)\n",
    "        vector_store.save_local(\"faiss_db\")\n",
    "#Agent对话链 + 工具调用（核心 RAG）\n",
    "\n",
    "    def get_conversational_chain(tools, ques):\n",
    "        llm = init_chat_model(\"deepseek-chat\", model_provider=\"deepseek\")\n",
    "        prompt = ChatPromptTemplate.from_messages([\n",
    "            (\n",
    "                \"system\",\n",
    "                \"\"\"你是AI助手，请根据提供的上下文回答问题，确保提供所有细节，如果答案不在上下文中，请说\"答案不在上下文中\"，不要提供错误的答案\"\"\",\n",
    "            ),\n",
    "            (\"placeholder\", \"{chat_history}\"),\n",
    "            (\"human\", \"{input}\"),\n",
    "            (\"placeholder\", \"{agent_scratchpad}\"),\n",
    "        ])\n",
    "        tool = [tools]\n",
    "        agent = create_tool_calling_agent(llm, tool, prompt)\n",
    "        agent_executor = AgentExecutor(agent=agent, tools=tool, verbose=True)\n",
    "        \n",
    "        response = agent_executor.invoke({\"input\": ques})\n",
    "        print(response)\n",
    "        st.write(\"🤖 回答: \", response['output'])\n",
    "#检查数据库是否存在\n",
    "    def check_database_exists():\n",
    "        \"\"\"检查FAISS数据库是否存在\"\"\"\n",
    "        return os.path.exists(\"faiss_db\") and os.path.exists(\"faiss_db/index.faiss\")\n",
    "#用户提问逻辑（调用 FAISS）\n",
    "    def user_input(user_question):\n",
    "        # 检查数据库是否存在\n",
    "        if not check_database_exists():\n",
    "            st.error(\"❌ 请先上传PDF文件并点击'Submit & Process'按钮来处理文档！\")\n",
    "            st.info(\"💡 步骤：1️⃣ 上传PDF → 2️⃣ 点击处理 → 3️⃣ 开始提问\")\n",
    "            return\n",
    "        \n",
    "        try:\n",
    "            # 加载FAISS数据库\n",
    "            new_db = FAISS.load_local(\"faiss_db\", embeddings, allow_dangerous_deserialization=True)\n",
    "            \n",
    "            retriever = new_db.as_retriever()\n",
    "            retrieval_chain = create_retriever_tool(retriever, \"pdf_extractor\", \"This tool is to give answer to queries from the pdf\")\n",
    "            get_conversational_chain(retrieval_chain, user_question)\n",
    "            \n",
    "        except Exception as e:\n",
    "            st.error(f\"❌ 加载数据库时出错: {str(e)}\")\n",
    "            st.info(\"请重新处理PDF文件\")\n",
    "#主界面逻辑（Streamlit）\n",
    "    def main():\n",
    "        st.set_page_config(\"🤖 LangChain B站公开课 By九天Hector\")\n",
    "        st.header(\"🤖 LangChain B站公开课 By九天Hector\")\n",
    "        \n",
    "        # 显示数据库状态\n",
    "        col1, col2 = st.columns([3, 1])\n",
    "        \n",
    "        with col1:\n",
    "            if check_database_exists():\n",
    "            pass\n",
    "            else:\n",
    "                st.warning(\"⚠️ 请先上传并处理PDF文件\")\n",
    "        \n",
    "        with col2:\n",
    "            if st.button(\"🗑️ 清除数据库\"):\n",
    "                try:\n",
    "                    import shutil\n",
    "                    if os.path.exists(\"faiss_db\"):\n",
    "                        shutil.rmtree(\"faiss_db\")\n",
    "                    st.success(\"数据库已清除\")\n",
    "                    st.rerun()\n",
    "                except Exception as e:\n",
    "                    st.error(f\"清除失败: {e}\")\n",
    "\n",
    "        # 用户问题输入\n",
    "        user_question = st.text_input(\"💬 请输入问题\", \n",
    "                                    placeholder=\"例如：这个文档的主要内容是什么？\",\n",
    "                                    disabled=not check_database_exists())\n",
    "\n",
    "        if user_question:\n",
    "            if check_database_exists():\n",
    "                with st.spinner(\"🤔 AI正在分析文档...\"):\n",
    "                    user_input(user_question)\n",
    "            else:\n",
    "                st.error(\"❌ 请先上传并处理PDF文件！\")\n",
    "\n",
    "        # 侧边栏\n",
    "        with st.sidebar:\n",
    "            st.title(\"📁 文档管理\")\n",
    "            \n",
    "            # 显示当前状态\n",
    "            if check_database_exists():\n",
    "                st.success(\"✅ 数据库状态：已就绪\")\n",
    "            else:\n",
    "                st.info(\"📝 状态：等待上传PDF\")\n",
    "            \n",
    "            st.markdown(\"---\")\n",
    "            \n",
    "            # 文件上传\n",
    "            pdf_doc = st.file_uploader(\n",
    "                \"📎 上传PDF文件\", \n",
    "                accept_multiple_files=True,\n",
    "                type=['pdf'],\n",
    "                help=\"支持上传多个PDF文件\"\n",
    "            )\n",
    "            \n",
    "            if pdf_doc:\n",
    "                st.info(f\"📄 已选择 {len(pdf_doc)} 个文件\")\n",
    "                for i, pdf in enumerate(pdf_doc, 1):\n",
    "                    st.write(f\"{i}. {pdf.name}\")\n",
    "            \n",
    "            # 处理按钮\n",
    "            process_button = st.button(\n",
    "                \"🚀 提交并处理\", \n",
    "                disabled=not pdf_doc,\n",
    "                use_container_width=True\n",
    "            )\n",
    "#提交 PDF 后执行的逻辑            \n",
    "            if process_button:\n",
    "                if pdf_doc:\n",
    "                    with st.spinner(\"📊 正在处理PDF文件...\"):\n",
    "                        try:\n",
    "                            # 读取PDF内容\n",
    "                            raw_text = pdf_read(pdf_doc)\n",
    "                            \n",
    "                            if not raw_text.strip():\n",
    "                                st.error(\"❌ 无法从PDF中提取文本，请检查文件是否有效\")\n",
    "                                return\n",
    "                            \n",
    "                            # 分割文本\n",
    "                            text_chunks = get_chunks(raw_text)\n",
    "                            st.info(f\"📝 文本已分割为 {len(text_chunks)} 个片段\")\n",
    "                            \n",
    "                            # 创建向量数据库\n",
    "                            vector_store(text_chunks)\n",
    "                            \n",
    "                            st.success(\"✅ PDF处理完成！现在可以开始提问了\")\n",
    "                            st.balloons()\n",
    "                            st.rerun()\n",
    "                            \n",
    "                        except Exception as e:\n",
    "                            st.error(f\"❌ 处理PDF时出错: {str(e)}\")\n",
    "                else:\n",
    "                    st.warning(\"⚠️ 请先选择PDF文件\")\n",
    "            \n",
    "            # 使用说明\n",
    "            with st.expander(\"💡 使用说明\"):\n",
    "                st.markdown(\"\"\"\n",
    "                **步骤：**\n",
    "                1. 📎 上传一个或多个PDF文件\n",
    "                2. 🚀 点击\"Submit & Process\"处理文档\n",
    "                3. 💬 在主页面输入您的问题\n",
    "                4. 🤖 AI将基于PDF内容回答问题\n",
    "                \n",
    "                **提示：**\n",
    "                - 支持多个PDF文件同时上传\n",
    "                - 处理大文件可能需要一些时间\n",
    "                - 可以随时清除数据库重新开始\n",
    "                \"\"\")\n",
    "\n",
    "    if __name__ == \"__main__\":\n",
    "        main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
